//@@***********************************************************************************@@
/*
*** FILE NAME     : proj3_durfey_komosinski_wilson.cpp
*** DATE          : Mar. 26 2010
*** WRITTEN By    : Stephen Durfey
		    Nicholas Komosinski
		    Rebekah Wilson
*** COURSE        : CS4080 - Advanced Operating Systems
*** INSTRUCTOR    : Dr. Jake  Lee
*** ASSIGNMENT    : Programming Assignment #3 - Process Synchronization
*** DESCRIPTION   : This assignment is a demonstration of process synchronization
		    using semaphores. This particular example is the "Cigarette-Smokers
		    Problem." 
		    	The system has three "smoker" processes and one "agent" process.
		    Each smoker continuously rolls a cigarette and then smokes it. But
		    to roll a cigarette, the smoker needs three ingredients: tobacco, 
		    paper, and matches. One of the smoker processes has paper, another 
		    has tobacco, and the thirs has matches. The agent has an infinite 
		    supply of all three materials. The agent places two of the
		    ingredients on the table. The smoker who has the remaining
		    ingredients then makes and smokes a cigarette, signaling the agent
		    on completion. The agent then puts out another two of the three
		    ingredients, and the cycle repeats.
*/
//@@***********************************************************************************@@

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <time.h>

union semun
{
	int			val;
	struct semid_ds	 	*buf;
	unsigned short		*array;
	struct sem_info		*__buf;
};

void initializeSemaphore(int, int);
int createSemaphore(key_t);
void deleteSemaphore(int, union semun);
int wait(int);
int post(int);
int randNum(int);

int main(int argc, char **argv)
{
  int agent, tobacco_owner, match_owner, paper_owner, table_lock;
  pid_t agentPid;
  int smoker = 0;
  union semun sem_union, sem_union2, sem_union3, sem_union4, sem_union5;


  /* create, initialize semaphore */
  agent		= createSemaphore((key_t)1234);
  tobacco_owner = createSemaphore((key_t)1235);
  match_owner 	= createSemaphore((key_t)1236);
  paper_owner 	= createSemaphore((key_t)1237);
  table_lock	= createSemaphore((key_t)1238);

  initializeSemaphore(1, table_lock);
  initializeSemaphore(0, agent);
  initializeSemaphore(0, tobacco_owner);
  initializeSemaphore(0, match_owner);
  initializeSemaphore(0, paper_owner);

  agentPid = getpid();

  if(getpid() == agentPid)
  {

	  //tobacco owner
	  fork();
	  if(getpid() != agentPid)
	  {
		  while(true)
		  {
			  //wait until smoker is signaled to take ingredients and smoke
			  wait(tobacco_owner);
			  //lock the table so no other processes can enter
			  wait(table_lock);
			  printf("tobacco picks up paper and matches\nsmoke\n");
			  //inform agent that ingredients have been picked up
			  post(agent);
			  //allow next process to enter table
			  post(table_lock);
		  }
		  exit(0);
	  }

	  //paper owner
	  fork();
	  if(getpid() != agentPid)
	  {
		  while(true)
		  {
			  //wait until smoker is signaled to take ingredients and smoke
			  wait(paper_owner);
			  //lock the table so no other processes can enter
			  wait(table_lock);
			  printf("paper picks up tobacco and matches\nsmoke\n");
			  //inform agent that ingredients have been picked up
			  post(agent);
			  //allow next process to enter table
			  post(table_lock);
		  }
		  exit(0);
	  }

	  //match owner
	  fork();
	  if(getpid() != agentPid)
	  {
		  while(true)
		  {
			  //wait until smoker is signaled to take ingredients and smoke
			  wait(match_owner);
			  //lock the table so no other processes can enter
			  wait(table_lock);
			  printf("match picks up tobacco and paper\nsmoke\n");
			  //inform agent that ingredients have been picked up
			  post(agent);
			  //allow next process to enter table
			  post(table_lock);
		  }
		  exit(0);
	  }
  }

  if(getpid() == agentPid)
  {

	  while(true)
	  {
		  //prevent other processes from entering their critical section
		  //before they are allowed to
		  wait(table_lock);
		  //determine which smoker to signal, this is a simulation
		  //of randomly throwing down two ingredients to a cigarette
		  sleep(2);
		  smoker = randNum(3);
		  //signal the smoker that the ingredients on the table are
		  //theirs to use

		  switch(smoker)
		  {
			  case 0:	post(tobacco_owner);
					break;
			  case 1:	post(match_owner);
					break;
			  case 2:	post(paper_owner);
					break;
		  }

		  post(table_lock);

		  //agent waits until ingredients have been picked up
		  wait(agent);
	  }

  }

  waitpid(-1, 0, 0);


  //delete semaphores
  deleteSemaphore(table_lock, sem_union);
  deleteSemaphore(agent, sem_union2);
  deleteSemaphore(tobacco_owner, sem_union3);
  deleteSemaphore(match_owner, sem_union4);
  deleteSemaphore(paper_owner, sem_union5);

  exit(0);
}

//returns a random number based upon the value passed in
int randNum(int highest)
{
	srand(time(NULL));

	return rand() % highest;
}

//creates a semaphore and returns the sem id
int createSemaphore(key_t key)
{
	return semget(key, 1, 0666|IPC_CREAT);
}

//initializes the semaphore, based on which id is passed in
//and what the starting value should be
void initializeSemaphore(int startValue, int sem)
{
	union semun sem_union;

	sem_union.val = startValue;
	semctl(sem, 0, SETVAL, sem_union);
}

//decrements the value of the sem id passed in
int wait(int sem)
{
	struct sembuf sem_b;
	//printf("semaphore %d\n", sem);

	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;


	//if an error occurred while decrementing the value
	//quit the program
	if(semop(sem, &sem_b, 1) == -1)
	{
		perror("Semaphore wait failed\n");
		return(0);
	}
	return 1;
}

//increment the value of the sem id passed in
int post(int sem)
{
	struct sembuf sem_b;

	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;

	if(semop(sem, &sem_b, 1) == -1)
	{
		perror("Semaphore post failed");
		return(0);
	}
	return 1;
}

void deleteSemaphore(int sem, union semun sem_union)
{
	semctl(sem, 0, IPC_RMID, sem_union);
}

